package org.zztl.json.parser;

import org.zztl.json.JSONException;

public class Lexer {

    private static final char EOF = (char) -1;

    private String input;

    private int p = 0;
    private char c;

    public Lexer(String input) {
        this.input = input;
        c = input.charAt(p);
    }

    public final Token nextToken() {
        for (; ; ) {
            if (c == '"') {
                next();
                return scanString();
            }
            if (c == ',') {
                next();
                return new Token(Token.COMMA, ",");
            }

            if (c >= '0' && c <= '9') {
                return scanNumber();
            }

            if (c == '-') {
                return scanNumber();
            }

            switch (c) {
                case ' ':
                case '\t':
                case '\b':
                case '\f':
                case '\n':
                case '\r':
                    next();
                    break;
                case '\'':
                    next();
                    return scanStringSingleQuote();
                case 't': // true
                    return scanTrue();
                case 'f': // false
                    return scanFalse();
                case 'n': // null
                    return scanNull();
                case '[':
                    next();
                    return new Token(Token.LBRACKET, "[");
                case ']':
                    next();
                    return new Token(Token.RBRACKET, "]");
                case '{':
                    next();
                    return new Token(Token.LBRACE, "{");
                case '}':
                    next();
                    return new Token(Token.RBRACE, "}");
                case ':':
                    next();
                    return new Token(Token.COLON, ":");
                default:
                    if (c == EOF) {
                        return new Token(Token.EOF, "EOF");
                    }
                    throw new JSONException("unknown symbol");
            }
        }
    }

    private Token scanNumber() {

        int pos = p;
        if (c == '-') {
            next();
        }

        for (; ; ) {
            if (c >= '0' && c <= '9') {
                next();
            } else {
                break;
            }
        }

        boolean isDouble = false;

        if (c == '.') {
            next();
            isDouble = true;
            for (; ; ) {
                if (c >= '0' && c <= '9') {
                    next();
                } else {
                    break;
                }
            }
        }

        if (isDouble) {
            return new Token(Token.LITERAL_FLOAT, input.substring(pos, p));
        } else {
            return new Token(Token.LITERAL_INT, input.substring(pos, p));
        }
    }

    private Token scanNull() {
        if (c != 'n') {
            throw new JSONException("error parse null");
        }
        next();
        if (c != 'u') {
            throw new JSONException("error parse null");
        }
        next();
        if (c != 'l') {
            throw new JSONException("error parse null");
        }
        next();
        if (c != 'l') {
            throw new JSONException("error parse null");
        }
        next();
        if (c == ' ' || c == ',' || c == '}' || c == ']' || c == '\n' || c == '\r' || c == '\t'
                || c == '\f' || c == '\b' || c == ':') {
            return new Token(Token.NULL, "null");
        } else {
            throw new JSONException("scan null error");
        }
    }

    private Token scanFalse() {
        if (c != 'f') {
            throw new JSONException("error parse false");
        }
        next();
        if (c != 'a') {
            throw new JSONException("error parse false");
        }
        next();
        if (c != 'l') {
            throw new JSONException("error parse false");
        }
        next();
        if (c != 's') {
            throw new JSONException("error parse false");
        }
        next();
        if (c != 'e') {
            throw new JSONException("error parse false");
        }
        next();
        if (c == ' ' || c == ',' || c == '}' || c == ']' || c == '\n' || c == '\r' || c == '\t'
                || c == '\f' || c == '\b' || c == ':') {
            return new Token(Token.FALSE, "false");
        } else {
            throw new JSONException("scan true error");
        }
    }

    private Token scanTrue() {
        if (c != 't') {
            throw new JSONException("error parse true");
        }
        next();
        if (c != 'r') {
            throw new JSONException("error parse true");
        }
        next();
        if (c != 'u') {
            throw new JSONException("error parse true");
        }
        next();
        if (c != 'e') {
            throw new JSONException("error parse true");
        }
        next();

        if (c == ' ' || c == ',' || c == '}' || c == ']' || c == '\n' || c == '\r' || c == '\t'
                || c == '\f' || c == '\b' || c == ':') {
            return new Token(Token.TRUE, "true");
        } else {
            throw new JSONException("scan true error");
        }
    }


    private Token scanString() {
        int pos = p;
        while (c != '"') {
            next();
            if (c == EOF) {
                throw new JSONException("syntax error");
            }
        }
        next();
        return new Token(Token.LITERAL_STRING, input.substring(pos, p - 1));
    }

    private Token scanStringSingleQuote() {
        int pos = p;
        while (c != '\'') {
            next();
            if (c == EOF) {
                throw new JSONException("syntax error");
            }
        }
        next();
        return new Token(Token.LITERAL_STRING, input.substring(pos, p - 1));
    }

    public void next() {
        p++;
        if (p >= input.length()) {
            c = EOF;
        } else {
            c = input.charAt(p);
        }
    }

    public final void skipWhitespace() {
        while (c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == '\b') {
            next();
        }
    }

}
